/*
* Copyright (C) Microsoft. All rights reserved.  
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not 
* use this file except in compliance with the License.  You may obtain a copy 
* of the License at http://www.apache.org/licenses/LICENSE-2.0  
* 
* THIS CODE IS PROVIDED *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED 
* WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE, 
* MERCHANTABLITY OR NON-INFRINGEMENT. 
*
* See the Apache License, Version 2.0 for specific language governing 
* permissions and limitations under the License.
*
*/
#include "pch.h"
#include <baja/transition_tracking/transition_tracking_service.h>
#include <baja/assert.hpp>

namespace baja { namespace transition_tracking {

// TRACK: bug 479 : need to use console tracing.
#define DEBUG_CONSOLE_TRACE(x)
#define DEBUG_CONSOLE_TRACE_F(X)

#ifndef DEBUG_CONSOLE_TRACE
#define DEBUG_CONSOLE_TRACE(x) OutputDebugString(x)
#define DEBUG_CONSOLE_TRACE_F(x) OutputDebugString((x).str().c_str())
#endif

transitions_bundle::transitions_bundle()
{
}

std::shared_ptr<itransitions_bundle> transitions_bundle::create()
{
    return std::dynamic_pointer_cast<itransitions_bundle>( std::make_shared<transitions_bundle>() );
}

void transitions_bundle::addTransition(
    const std::vector<transition_path_node>& absolutePath,
    const std::shared_ptr<itransition_parameters>& transitionParameters
    )
{
    BAJA_ASSERT(absolutePath.size() != 0, L"empty path is an invalid value");

    std::shared_ptr<transition> newTransition = std::make_shared<transition>();
    newTransition->absolutePath = absolutePath;
    newTransition->transitionParameters = transitionParameters;
    _undoQueue.push(newTransition);
}

void transitions_bundle::undo(
    const std::shared_ptr<itransition_target>& rootTarget
    )
{
    DEBUG_CONSOLE_TRACE(L"-UNDO------------------------------------------\n");
    DEBUG_CONSOLE_TRACE_F(
        boost::wformat(L"transitions_bundle::undo() : queue size = %i\n") %
        _undoQueue.size()
        );

    while (_undoQueue.size() != 0)
    {
        DEBUG_CONSOLE_TRACE(L"---CMD-------------------------------------------\n");

        std::shared_ptr<transition>& lastEvent = _undoQueue.top();
        _redoQueue.push(lastEvent);
        _undoQueue.pop();

        rootTarget->undo(lastEvent->absolutePath, 0, lastEvent->transitionParameters);
    }
}

void transitions_bundle::redo(
    const std::shared_ptr<itransition_target>& rootTarget
    )
{
    DEBUG_CONSOLE_TRACE(L"-REDO------------------------------------------\n");
    DEBUG_CONSOLE_TRACE_F(
        boost::wformat(L"transitions_bundle::redo() : queue size = %i\n") %
        _redoQueue.size()
        );

    while (_redoQueue.size() != 0)
    {
        DEBUG_CONSOLE_TRACE(L"---CMD-------------------------------------------\n");

        std::shared_ptr<transition>& lastEvent = _redoQueue.top();
        _undoQueue.push(lastEvent);
        _redoQueue.pop();

        rootTarget->redo(lastEvent->absolutePath, 0, lastEvent->transitionParameters);
    }
}

transition_tracking_service::transition_tracking_service() :
    _typeTokenSeed(0),
    _nestingLevel(0),
    _undoRedoInProgress(false)
{
}

std::shared_ptr<transition_tracking_service> transition_tracking_service::create()
{
    std::shared_ptr<transition_tracking_service> returnValue = std::make_shared<transition_tracking_service>();
    return returnValue;
}

void transition_tracking_service::initialize()
{
}

void transition_tracking_service::registerRootTransitionTarget(
    std::shared_ptr<itransition_target> rootTarget
    )
{
    _rootTarget = rootTarget;
}

baja::uint32 transition_tracking_service::generateTargetTypeToken()
{
    return ++_typeTokenSeed;
}

void transition_tracking_service::clearRedoQueue()
{
    while (_redoBundleQueue.size() != 0)
    {
        _redoBundleQueue.pop();
    }
}

void transition_tracking_service::addTransitionsBundle(
    const std::shared_ptr<itransitions_bundle>& transitionBundle
    )
{
    clearRedoQueue();

    // do not push into the undo queue for now since undo/redo is not supported.  This queue is growing infinitely big
    // as the user is performing actions.  If the user added a photo, the memory stream of the photo is held on indefinitely
    // for the duration of the app.
    //_undoBundleQueue.push(std::dynamic_pointer_cast<iinternal_transitions_bundle>(transitionBundle));
}

void transition_tracking_service::undo()
{
    if (_undoBundleQueue.size() == 0)
    {
        return;
    }

    undo_redo_context urc(&_undoRedoInProgress);

    std::shared_ptr<iinternal_transitions_bundle> bundle = _undoBundleQueue.top();
    _redoBundleQueue.push(bundle);
    _undoBundleQueue.pop();

    bundle->undo(_rootTarget);
}

void transition_tracking_service::redo()
{
    if (_redoBundleQueue.size() == 0)
    {
        return;
    }

    undo_redo_context urc(&_undoRedoInProgress);

    std::shared_ptr<iinternal_transitions_bundle> bundle = _redoBundleQueue.top();
    _undoBundleQueue.push(bundle);
    _redoBundleQueue.pop();

    bundle->redo(_rootTarget);
}

void transition_tracking_service::startTransitionBundle()
{
    if (_undoRedoInProgress == true)
    {
        return;
    }

    ++_nestingLevel;
    if (_currentBundle == nullptr)
    {
        _currentBundle = transitions_bundle::create();
    }
}

void transition_tracking_service::endTransitionBundle()
{
    if (_undoRedoInProgress == true)
    {
        return;
    }

    --_nestingLevel;

    if (_nestingLevel == 0)
    {
        addTransitionsBundle(_currentBundle);
        _currentBundle = nullptr;
    }
}

void transition_tracking_service::addTransition(
    const std::vector<transition_path_node>& absolutePath,
    const std::shared_ptr<itransition_parameters>& transitionParameters
    )
{
    if (_undoRedoInProgress == true)
    {
        return;
    }

    if (_currentBundle == nullptr)
    {
        // we need to be forgiving here since in some scenario, no undo-able action
        // has been define.
        return;
    }

    _currentBundle->addTransition(absolutePath, transitionParameters);
}

std::shared_ptr<itransition_tracking_service> baja::transition_tracking::createTransitionTrackingService()
{
    std::shared_ptr<transition_tracking_service> returnValue;
    returnValue = transition_tracking_service::create();
    returnValue->initialize();
    return std::dynamic_pointer_cast<itransition_tracking_service>(returnValue);
}

}} // baja::transition_tracking